using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Serialization;
using System.Windows.Forms;

using Microsoft.Build.BuildEngine;

namespace stsdev {

  #region "Custom Types used by SolutionBuilder"
  public enum SolutionVersionTypeEnum {
    VS2005,
    VS2008_30,
    VS2008_35
  }

  public enum AssemblyDeploymentTarget {
    WebApplication,
    GlobalAssemblyCache
  }
  public struct SolutionSafeControlEntry {
    public string Assembly;
    public string Namespace;
    public string TypeName;
    public string Safe;
  }

  public struct SolutionAssembly {
    public string Location;
    public string DeploymentTarget;
    public List<SolutionSafeControlEntry> SafeControlEntries;
  }
  #endregion

  public class SolutionBuilder {

    #region "Solution/Project Properties"

    private static ISolutionProvider _SolutionProvider;
    public static ISolutionProvider SolutionProvider {
      get { return _SolutionProvider; }
      set { _SolutionProvider = value; }
    }

    private static string _SolutionName;
    public static string SolutionName {
      get { return _SolutionName; }
      set { _SolutionName = value; }
    }

    private static string _SolutionDirectory;
    public static string SolutionDirectory {
      get { return _SolutionDirectory; }
      set { _SolutionDirectory = value; }
    }

    private static string _SolutionId;
    public static string SolutionId {
      get { return _SolutionId; }
      set { _SolutionId = value; }
    }

    private static SolutionVersionTypeEnum _SolutionVersionType = SolutionVersionTypeEnum.VS2005;
    public static SolutionVersionTypeEnum SolutionVersionType {
      get { return _SolutionVersionType; }
      set { _SolutionVersionType = value; }
    }

    private static string _ProjectGuid;
    public static string ProjectGuid {
      get { return _ProjectGuid; }
      set { _ProjectGuid = value; }
    }

    private static bool _ResetWebServer;
    public static bool ResetWebServer {
      get { return _ResetWebServer; }
      set { _ResetWebServer = value; }
    }

    private static bool _AssemblyDeployment = true;
    public static bool AssemblyDeployment {
      get { return _AssemblyDeployment; }
      set { 
        _AssemblyDeployment = value;
        if ( value == false ) {
          _SafeControlSettings = false;
          _CasPermissions = false;
        }
      }
    }

    private static string _KeyFileSource;
    public static string KeyFileSource {
      get { return _KeyFileSource; }
      set { 
        _KeyFileSource = value;
        _KeyFileName = value.Substring(value.LastIndexOf(@"\") + 1);
      }
    }

    private static string _KeyFileName;
    public static string KeyFileName {
      get { return _KeyFileName; }
      //set { _KeyFileName = value; }
    }

    private static bool _SafeControlSettings = false;
    public static bool SafeControlSettings {
      get { return _SafeControlSettings; }
      set { _SafeControlSettings = value; }
    }

      private static XmlNode _DwpFilesNode;
      public static XmlNode DwpFilesNode
      {
          get { return _DwpFilesNode; }
          set { _DwpFilesNode = value; }
      }

    private static AssemblyDeploymentTarget _DeploymentTarget = AssemblyDeploymentTarget.GlobalAssemblyCache;
    public static AssemblyDeploymentTarget DeploymentTarget {
      get { return _DeploymentTarget; }
      set { _DeploymentTarget = value; }
    }

    private static bool _CasPermissions = false;
    public static bool CasPermissions {
      get { return _CasPermissions; }
      set { _CasPermissions = value; }
    }

    private static XmlNode _CasPermissionsNode;
    public static XmlNode CasPermissionsNode {
      get { return _CasPermissionsNode; }
      set { _CasPermissionsNode = value; }
    }

    private static string _OutputDirectory;
    public static string OutputDirectory
    {
        get { return _OutputDirectory; }
        set { _OutputDirectory = value; }
    }


    private static string _TargetName;
    public static string TargetName {
      get { return _TargetName; }
      set { _TargetName = value; }
    }

    private static string _TargetPath;
    public static string TargetPath {
      get { return _TargetPath; }
      set {
        _TargetPath = value;
        Assembly asm = Assembly.LoadFile(_TargetPath);
        FullAssemblyName = asm.FullName;
      }
    }

    private static string _FullAssemblyName;
    public static string FullAssemblyName {
      get { return _FullAssemblyName; }
      set { _FullAssemblyName = value; }
    }

    private static List<SolutionAssembly> _Assemblies = new List<SolutionAssembly>();
    public static List<SolutionAssembly> Assemblies {
      get { return _Assemblies; }
      set { _Assemblies = value; }
    }

    private static string _StsAdmDeployParameters = "-immediate";
    public static string StsAdmDeployParameters {
      get { return _StsAdmDeployParameters; }
      set { _StsAdmDeployParameters = value; }
    }

    private static string _StsAdmUpgradeParameters = "-local";
    public static string StsAdmUpgradeParameters {
      get { return _StsAdmUpgradeParameters; }
      set { _StsAdmUpgradeParameters = value; }
    }

    private static string _StsAdmRetractParameters = "-immediate";
    public static string StsAdmRetractParameters {
      get { return _StsAdmRetractParameters; }
      set { _StsAdmRetractParameters = value; }
    }

    #endregion

    #region "Visual Studio Configurations"

    public static string[] Configurations = 
      { "DebugBuild",
        "DebugInstall",
        "DebugDeploy",
        "DebugRedeploy",
        "DebugUpgrade",
        "DebugQuickCopy",
        "DebugRetract",
        "DebugDelete",
        "ReleaseBuild"};
    #endregion

    #region "Project Assembly References List"
    private static string[] DefaultProjectAssemblyReferences = 
      { "System",
        "System.Web",
        "System.Xml",
        "Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL"};
    public static List<string> ProjectAssemblyReferences = new List<string>(DefaultProjectAssemblyReferences);
    #endregion

    #region "Solution Provider List"
    
    private static ISolutionProvider[] _SolutionProviders =
      { new EmptySolutionProvider(),        
        new EmptySolutionWithAssemblyProvider(),
        new ImageOnlySolutionProvider(),
        new SimpleFeatureSolutionProvider(),
        new FeatureWithApplicationPageSolutionProvider(),
        new WebPartSolutionProvider(),
        new WebPartWithCasPermissionsSolutionProvider(),
        new WebServiceProvider() };
    
    private static List<ISolutionProvider> 
      SolutionProviders = new List<ISolutionProvider>(_SolutionProviders);

    private static void InitializeExternalSolutionProviders() {
      string StsDevExePath = Assembly.GetExecutingAssembly().Location;
      string StsDevDirectory = StsDevExePath.Substring(0, StsDevExePath.LastIndexOf(@"\"));
      string SolutionProvidersXmlPath = StsDevDirectory + @"\SolutionProviders.xml";
      
      if (File.Exists(SolutionProvidersXmlPath)) {
        XmlDocument doc = new XmlDocument();
        doc.Load(SolutionProvidersXmlPath);
        XmlElement RootElement = doc.DocumentElement;
        foreach (XmlNode ChildNode in RootElement.ChildNodes) {
          if (ChildNode.Name.Equals("Clear")) {
            SolutionProviders.Clear();
          }
          if (ChildNode.Name.Equals("Add")) {
            string ClassName = ChildNode.Attributes["Class"].Value;
            string AssemblyName = ChildNode.Attributes["Assembly"].Value;
            SolutionProviders.Add(LoadSolutionProvider(ClassName, AssemblyName));
          }
        }
      }     
    }

    private static ISolutionProvider LoadSolutionProvider(string ClassName, string AssemblyName) {
      Assembly asm = Assembly.Load(AssemblyName);      
      Type ProviderClass = asm.GetType(ClassName);
      Type[] ConstructorArgTypes = { };
      ConstructorInfo ProviderConstructor = ProviderClass.GetConstructor(ConstructorArgTypes);
      object[] ConstructorArgs = { };
      return (ISolutionProvider)ProviderConstructor.Invoke(ConstructorArgs);
    }

    #endregion

    public static void RunCreateSolutionWizard() {

      InitializeExternalSolutionProviders();

      SelectSolutionType dlg = new SelectSolutionType();
      dlg.InitializeSolutionProviders(SolutionProviders);
      DialogResult result = dlg.ShowDialog();
      if (result == DialogResult.OK) {
        // create standard project structure
        SolutionName = dlg.GetSolutionName();
        SolutionDirectory = dlg.GetParentDirectory() + @"\" + SolutionName;
        SolutionVersionType = dlg.GetSolutionVersionType();
        SolutionProvider = dlg.GetSelectedProvider();
        KeyFileSource = dlg.GetSigningKeyFile();
        // create standard solution/project structure
        InitializeSolution();
        // call upon SolutionProvider to create solution
        SolutionProvider.AddSolutionItems();
        // fanalize building solution
        CompleteSolution();
        string SuccessMessage = "New solution " +
                                SolutionName +
                                " successfully created at " +
                                SolutionDirectory;
        MessageBox.Show(SuccessMessage, "STSDEV");
      }
      else {
        MessageBox.Show("STSDEV Wizard Cancelled by User", "Goodbye from STSDEV");
      }

    }

    public static void InitializeSolution() {
      // 
      SolutionBuilder.SolutionProvider.InitializeSolution();
      // create GUID for project
      ProjectGuid = Guid.NewGuid().ToString().ToUpper();
      // create solution directory
      Directory.CreateDirectory(SolutionDirectory);
      Directory.SetCurrentDirectory(SolutionDirectory);
      // create DeploymentFiles directory
      Directory.CreateDirectory(Globals.DeploymentFilesFolder);
      ProjectFileBuilder.AddFolder(Globals.DeploymentFilesFolder);
      // create RootFiles directory
      Directory.CreateDirectory(Globals.RootFilesFolder);
      ProjectFileBuilder.AddFolder(Globals.RootFilesFolder);

      if (AssemblyDeployment) {
        // create AssemblyInfo.cs      
        AssemblyInfoBuilder.Create();

        // add Strong Name Key file
          //We do not need to set the keyfilename because it is set when the KeyFileSource value is set.
        //KeyFileName = "KeyFile.snk";
        StrongNameKeyBuilder.AddKeyFileToProject();
        

        // create .csproj file for initial build of assembly1        
        ProjectFileBuilder.Create(SolutionBuilder.SolutionName, ProjectGuid);

        // build assembly so it can be loaded and inspected using reflection
        SolutionBuilder.BuildProjectAssembly();

        // set target name and path after assembly compilation
        SolutionBuilder.TargetName = SolutionBuilder.SolutionName;
        SolutionBuilder.TargetPath = SolutionBuilder.SolutionDirectory +
                                     Globals.DebugTargetPath + @"\" +
                                     SolutionBuilder.SolutionName + ".dll";
      }

      SolutionBuilder.StsAdmDeployParameters = "-immediate";
      SolutionBuilder.StsAdmUpgradeParameters = "-local";
      SolutionBuilder.StsAdmRetractParameters = "-immediate";

      if (AssemblyDeployment) {
        SolutionBuilder.StsAdmDeployParameters += " -allowgacdeployment";
        SolutionBuilder.StsAdmUpgradeParameters += " -allowgacdeployment";
      }
      if (CasPermissions) {
        SolutionBuilder.StsAdmDeployParameters += " -allowcaspolicies";
        SolutionBuilder.StsAdmUpgradeParameters += " -allowcaspolicies";
      }
      if (SafeControlSettings || DeploymentTarget == AssemblyDeploymentTarget.WebApplication) {
        SolutionBuilder.StsAdmDeployParameters += " -allcontenturls";
        SolutionBuilder.StsAdmRetractParameters += " -allcontenturls";
      }

      PermissionSet CasPermissionSet = null;
      if (CasPermissions && (SolutionProvider is IPartiallyTrustedSolutionProvider)) {
        CasPermissionSet = ((IPartiallyTrustedSolutionProvider)SolutionProvider).CreatePermissionSet();
      }

      CreateDeploymentFiles(AssemblyDeployment, SafeControlSettings, DeploymentTarget, CasPermissionSet);
      
    }

    public static void BuildProjectAssembly() {
      Engine engine = null;
      if (SolutionBuilder.SolutionVersionType == SolutionVersionTypeEnum.VS2008_35) {
        engine = new Engine(@"C:\WINDOWS\Microsoft.NET\Framework\v3.5");
      }
      else {
        engine = new Engine(@"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727");
      }
      FileLogger logger = new FileLogger();
      logger.Parameters = @"logfile=BuildInfo.txt";
      engine.RegisterLogger(logger);
      // example: csc.exe /noconfig /nowarn:1701,1702 /errorreport:prompt /warn:4 /define:DEBUG;TRACE /reference:"C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\ISAPI\Microsoft.SharePoint.dll" /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Web.dll /reference:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll /debug+ /debug:full /optimize- /out:obj\Debug\@ProjectName.dll /target:library
      bool BuildSuccess = engine.BuildProjectFile(SolutionBuilder.SolutionName + ".csproj");
      engine.UnregisterAllLoggers();
    }

    public static void CreateDeploymentFiles(bool AssemblyDeployment, bool SafeControlSettings, AssemblyDeploymentTarget DeploymentTarget, PermissionSet CasPermissionSet) {

      // create SolutionConfig.xml
      SolutionConfigBuilder.Create(Globals.SolutionConfigPath, AssemblyDeployment, SafeControlSettings, DeploymentTarget, CasPermissionSet);
      ProjectFileBuilder.AddSourceFile(Globals.SolutionConfigPath, SourceFileTypeEnum.None);

      // load settings from SolutionConfig.xml
      LoadSolutionConfigFile();

      // create Microsoft.SharePoint.targets      
      TargetsBuilder.Create(Globals.TargetsFilePath);
      ProjectFileBuilder.AddSourceFile(Globals.TargetsFilePath, SourceFileTypeEnum.None);

      // create manifest.xml
      ManifestBuilder.Create(Globals.ManifestXmlPath, AssemblyDeployment);
      ProjectFileBuilder.AddSourceFile(Globals.ManifestXmlPath, SourceFileTypeEnum.None);

      // create SolutionPackage.ddf
      CabDdfBuilder.Create(Globals.SolutionPackageDdfPath);
      ProjectFileBuilder.AddSourceFile(Globals.SolutionPackageDdfPath, SourceFileTypeEnum.None);

    }

    public static void RefreshDeploymentFiles(string TargetSolutionName, string TargetSolutionDirectory, string currentConfiguration) {
        // initialize static properties of SolutionBuilder class
        SolutionName = TargetSolutionName;
        TargetName = TargetSolutionName;
        SolutionDirectory = TargetSolutionDirectory;

      // echo to output window      
      Console.WriteLine("Current Solution Name: " + SolutionName);
      Console.WriteLine("Current Solution Directory: " + SolutionDirectory);
      Console.WriteLine("Current Configuration: " + currentConfiguration);
      Console.WriteLine("Refreshing deployment files...");

      RefreshDeploymentFiles();
    }

    public static void RefreshDeploymentFiles() {
    
      // set current directory
      Directory.SetCurrentDirectory(SolutionDirectory);

      // load in settings from SolutionConfig.xml
      LoadSolutionConfigFile();
      string basePath = string.Empty;

        if(!string.IsNullOrEmpty(OutputDirectory))
        {
          basePath = string.Format("{0}{1}", OutputDirectory, (OutputDirectory.EndsWith(@"\") ? string.Empty : @"\"));
        }
        else
        {
            basePath = string.Format("{0}{1}{2}{3}", SolutionDirectory, (SolutionDirectory.EndsWith(@"\") ? string.Empty : @"\"), Globals.DebugTargetPath, (Globals.DebugTargetPath.EndsWith(@"\") ? string.Empty : @"\"));
        }

      if (AssemblyDeployment) {
        // set target path
          TargetPath = basePath + SolutionName + ".dll";
      }


      // create Manifest.xml
      Console.WriteLine("Rebuilding " + Globals.ManifestXmlPath);
      ManifestBuilder.Create(Globals.ManifestXmlPath, AssemblyDeployment, ResetWebServer);


      // create SolutionPackage.ddf
      Console.WriteLine("Rebuilding " + Globals.SolutionPackageDdfPath);
      CabDdfBuilder.Create(Globals.SolutionPackageDdfPath, basePath);

    }

    public static void LoadSolutionConfigFile() {
      // clear Assemblies collection
      _Assemblies.Clear();

      if (!File.Exists(Globals.SolutionConfigPath)) {
        SolutionConfigBuilder.Create(Globals.SolutionConfigPath, true);
      }

      XmlDocument SolutionConfig = new XmlDocument();
      SolutionConfig.Load(Globals.SolutionConfigPath);

      XmlNode SolutionIdNode = SolutionConfig.SelectSingleNode(@"/Solution/SolutionId");
      _SolutionId = SolutionIdNode.InnerText;
      
      XmlNode SolutionNameNode = SolutionConfig.SelectSingleNode(@"/Solution/SolutionName");
      _SolutionName = SolutionNameNode.InnerText;

      XmlNode ResetWebServerNode = SolutionConfig.SelectSingleNode(@"/Solution/ResetWebServer");
      _ResetWebServer = Convert.ToBoolean(ResetWebServerNode.InnerText);
     
      XmlNode AssemblyDeploymentNode = SolutionConfig.SelectSingleNode(@"/Solution/AssemblyDeployment");
      _AssemblyDeployment = Convert.ToBoolean(AssemblyDeploymentNode.InnerText);


      XmlNode SafeControlSettingsNode = SolutionConfig.SelectSingleNode(@"/Solution/SafeControlSettings");
      _SafeControlSettings = Convert.ToBoolean(SafeControlSettingsNode.InnerText);   

      XmlNode CasPermissionsEnabledNode = SolutionConfig.SelectSingleNode(@"/Solution/CasPermissions");
      _CasPermissions = Convert.ToBoolean(CasPermissionsEnabledNode.InnerText);

      if (_AssemblyDeployment) {
        XmlNode SolutionAssembliesNode = SolutionConfig.SelectSingleNode(@"/Solution/Assemblies");
        foreach (XmlNode SolutionAssemblyNode in SolutionAssembliesNode.ChildNodes) {
          SolutionAssembly asm;
          asm.Location = SolutionAssemblyNode.Attributes["Location"].Value;
          asm.DeploymentTarget = SolutionAssemblyNode.Attributes["DeploymentTarget"].Value;
          asm.SafeControlEntries = new List<SolutionSafeControlEntry>();
          foreach (XmlNode SafeControlEntriesNode in SolutionAssemblyNode.ChildNodes) {
            foreach (XmlNode SafeControlEntryNode in SafeControlEntriesNode.ChildNodes) {
              SolutionSafeControlEntry sce;
              sce.Assembly = SafeControlEntryNode.Attributes["Assembly"].Value;
              sce.Namespace = SafeControlEntryNode.Attributes["Namespace"].Value;
              sce.TypeName = SafeControlEntryNode.Attributes["TypeName"].Value;
              sce.Safe = SafeControlEntryNode.Attributes["Safe"].Value;
              asm.SafeControlEntries.Add(sce);
            }
          }
          _Assemblies.Add(asm);
        }
      }

      if (_CasPermissions) {
        CasPermissionsNode = SolutionConfig.SelectSingleNode(@"/Solution/CodeAccessSecurity/PolicyItem/PermissionSet");        
      }

        DwpFilesNode = SolutionConfig.SelectSingleNode(@"/Solution/DwpFiles");
    }

    public static void CompleteSolution() {
      // create project file
      ProjectFileBuilder.Create(SolutionBuilder.SolutionName, ProjectGuid);
      // create solution file
      SolutionFileBuilder.Create(SolutionBuilder.SolutionName, ProjectGuid);
      // refresh depoyment files
      SolutionBuilder.RefreshDeploymentFiles();
    }

  }
}
